-
Notifications
You must be signed in to change notification settings - Fork 13.7k
Allow borrowing array elements from packed structs with ABI align <= packed align #145419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
- Add fallback mechanism in is_disaligned() when layout computation fails - Handle TooGeneric errors by computing element alignment from type structure - Fix packed-array-const-generic.rs test to pass compilation - Resolves issue where const generic parameters couldn't be resolved during alignment checks
r? @SparrowLii rustbot has assigned @SparrowLii. Use |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, I’m the bug reporter. Just what I noticed on a first glance.
Ok(normalized) => normalized, | ||
Err(_) => { | ||
// If normalization fails, fall back to the original type | ||
ty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why? it feels a lot safer to conservatively return true
here, same as when layout_of
fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still relevant
// For const generic arrays like [u8; CAP], we can make a reasonable assumption | ||
// about their alignment based on the element type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're not making a reasonable assumption. Their layout literally only depend son the layout of their element type, doesn't it?
Getting this right is soundness critical, so we need to be confident about that we don#t introduce false negatives.
Also, please rename this function to is_potentially_disaligned
, to clarify that it's allowed to return false positives, but not false nnegatives
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use "misaligned" elsewhere in the compiler, maybe let's go with that instead of "disaligned" (which I never saw/heard before).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also still relevant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed feedback!
I agree with the soundness concern and with using the term “misaligned”.
I will:
- rename the function to
is_potentially_misaligned
and update call sites/docs to clarify that it may return false positives but must not return false negatives (soundness-critical), - avoid relaxing the check when
layout_of(ty)
fails in general.
To still address the motivating case without introducing false negatives, I’ll only add a very small, layout-free special-case:
when ty.kind()
is Array(elem, _)
and elem
is u8
or i8
(which have ABI alignment 1 by definition, independent of the array length), we treat the borrow as not potentially misaligned. All other cases remain conservative and return true
when layout_of(ty)
fails.
This fixes the [u8; CAP]
packed-struct case while keeping the function strictly conservative for any type whose ABI alignment could exceed the packed alignment.
If you’d prefer the even stricter variant (no special-casing at all), I can drop the exception and keep returning true
on layout_of
failure across the board.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ignoring the length and only checking the argument is fine. My issue was with the comment, not the behavior. We already guarantee that the alignment constraints are equal:
you can get a &T
out of an &[T]
, so [T]
must have stronger requirements than &T
you can get a &[T]
from a &T
via std::array::from_ref
, so T
must have stronger requirements than &[T]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I’ve updated the comment to formally justify that align([T]) == align(T),
using the two directions you mentioned (&[T] -> &T
and std::array::from_ref(&T)
).
Behavior remains unchanged: we only consider the element type’s ABI alignment and
ignore the array length.
… clarity; Unified to ty::Array(element_ty, _) | ty::Slice(element_ty) => …; Modify Chinese comments to English comments
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
Fixed:
Thanks |
I've updated the PR as requested (renamed function, updated comment, removed special-casing). Just let me know if further adjustments are needed. |
// Only allow u8 and i8 arrays when layout computation fails | ||
// Other types are conservatively assumed to be misaligned | ||
match element_ty.kind() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
properly compute the of the element_ty
here, limiting it to only u8 and i8 feels unnecessary 🤔
#![allow(dead_code)] | ||
|
||
#[repr(C, packed)] | ||
struct PascalStringI8<const CAP: usize> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please merge this into a single test and use generic-length
instead of const-generic
in the file name
cc @rust-lang/lang this causes us to no longer lint "due to a borrow of a potentially misaligned field" when borrowing from a I feel like this may be fine without an FCP as it seems like a small enough change 🤷 |
Thanks for the ping. Specifically, we were giving a hard error here, so allowing this is a stabilization. Since we're relatively caught up, it'll be as fast for us to FCP this as to decide not to do so, and lately we've generally been leaning toward FCPing everything that we technically should. We could waive our 10-day period, but it doesn't seem likely that'll affect what release this lands in, so let's just, in the normal way... |
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
Oh, yeah, thought this was a lint 👍 an FCP seems appropriate |
Yeah, if reverting it would be a breaking change, we've got to FCP the initial merge. 👍 to the general change of being smarter about alignment where possible. @rfcbot reviewed That said, I think we need the rules written up more. Since this is a hard error, it's something that would go into a spec so that other things could make the same checks. @rfcbot concern exact-rules-list Looking at the PR, it looks like it's actually only It reminds me of the experiments that have been done towards having niches for references, which wanted a query for alignment and size (or a sound approximation thereof) that would work even in cases where the full layout couldn't be calculated. This seems like it would like to have the same kind of thing, rather than adding a bunch of ad-hoc layout knowledge into this specific check. |
This comment has been minimized.
This comment has been minimized.
Thanks for the detailed feedback! I’ve updated the PR with the following:
I’ve also drafted the exact rules in the PR description and opened a Reference PR |
Does this PR create a new semver hazard? For example.. // In a remote crate
struct Remote(u8);
// In local crate
#[repr(packed)]
struct Local {
remote: Remote,
}
let remote = &Local::default().remote; Then @rfcbot concern semver hazard Otherwise, +1 for the overall idea. @rfcbot reviewed |
That semver hazard already exists outside of arrays. This PR just makes the logic that figures out the alignment of a type smarter: we can know the alignment of |
// Soundness: For any `T`, the ABI alignment requirement of `[T]` equals that of `T`. | ||
// Proof sketch: | ||
// (1) From `&[T]` we can obtain `&T`, hence align([T]) >= align(T). | ||
// (2) Using `std::array::from_ref(&T)` we can obtain `&[T; 1]` (and thus `&[T]`), | ||
// hence align(T) >= align([T]). | ||
// Therefore align([T]) == align(T). Length does not affect alignment. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Soundness: For any `T`, the ABI alignment requirement of `[T]` equals that of `T`. | |
// Proof sketch: | |
// (1) From `&[T]` we can obtain `&T`, hence align([T]) >= align(T). | |
// (2) Using `std::array::from_ref(&T)` we can obtain `&[T; 1]` (and thus `&[T]`), | |
// hence align(T) >= align([T]). | |
// Therefore align([T]) == align(T). Length does not affect alignment. | |
// We can't compute the layout of `T`. But maybe we can still compute the alignment: | |
// For any `T`, the ABI alignment requirement of `[T]` and `[T; N]` equals that of `T`. | |
// Length does not affect alignment. |
I don't think referencing standard library operations here makes much sense. Maybe we can reference the Reference wherever it defines the alignment of arrays, but TBH that doesn't seem necessary. What's relevant is to explicitly invoke the fact o that arrays are aligned like their element type, that's the one key reasoning step needed here. Let's not drown that in noise.
match ty.kind() { | ||
ty::Array(elem_ty, _) | ty::Slice(elem_ty) => { | ||
// Try to obtain the element's layout; if we can, use its ABI align. | ||
match tcx.layout_of(typing_env.as_query_input(*elem_ty)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend inlining this function at its only call site, this seems unnecessarily verbose.
If you want to make a helper function, it'd be get_type_align
containing the entire match tcx.layout_of(typing_env.as_query_input(ty)) {
.
} | ||
|
||
fn main() { | ||
// Run pass cases (fail cases only check diagnostics) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These never get run anyway since the file fails to compile, so this is quite pointless.
mod pass_i8 { | ||
#[repr(C, packed)] | ||
pub struct S<const N: usize> { | ||
buf: [i8; N], | ||
} | ||
pub fn run() { | ||
let s = S::<4> { buf: [1, 2, 3, 4] }; | ||
let _ok = &s.buf[..]; // no E0793 | ||
} | ||
} | ||
|
||
mod pass_nonzero_u8 { | ||
use super::*; | ||
#[repr(C, packed)] | ||
pub struct S<const N: usize> { | ||
buf: [NonZeroU8; N], | ||
} | ||
pub fn run() { | ||
let s = S::<3> { | ||
buf: [ | ||
NonZeroU8::new(1).unwrap(), | ||
NonZeroU8::new(2).unwrap(), | ||
NonZeroU8::new(3).unwrap(), | ||
], | ||
}; | ||
let _ok = &s.buf[..]; // no E0793 | ||
} | ||
} | ||
|
||
mod pass_maybeuninit_u8 { | ||
use super::*; | ||
#[repr(C, packed)] | ||
pub struct S<const N: usize> { | ||
buf: [MaybeUninit<u8>; N], | ||
} | ||
pub fn run() { | ||
let s = S::<2> { buf: [MaybeUninit::new(1), MaybeUninit::new(2)] }; | ||
let _ok = &s.buf[..]; // no E0793 | ||
} | ||
} | ||
|
||
mod pass_transparent_u8 { | ||
#[repr(transparent)] | ||
pub struct WrapU8(u8); | ||
|
||
#[repr(C, packed)] | ||
pub struct S<const N: usize> { | ||
buf: [WrapU8; N], | ||
} | ||
pub fn run() { | ||
let s = S::<2> { buf: [WrapU8(1), WrapU8(2)] }; | ||
let _ok = &s.buf[..]; // no E0793 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these already pass before your PR, so they don't actually test what you want them to test.
You need to make the N
generic, like in pass_u8
. (In the fail tests it should also be generic to ensure it hits the same codepath.)
pub fn run() { | ||
let p = PascalString::<10> { len: 3, buf: *b"abc\0\0\0\0\0\0\0" }; | ||
let s = bar(&p); | ||
assert_eq!(s, "abc"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no reason I can see for this function to exist.
Fixes #145376
Summary
This PR fixes a false positive E0793 ("reference to packed field is unaligned")
when borrowing from a
#[repr(C, packed)]
struct field whose type is a const-generic arraywith an element type that has ABI alignment <= the struct's packed alignment.
Example before this change: